{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "26e64d1c",
   "metadata": {},
   "source": [
    "\n",
    "# LangGraph Reflection 机制开发指南\n",
    "\n",
    "本指南详细介绍了如何在 **LangGraph** 中构建基于大语言模型（LLM）的 **Reflection（反思）** 机制。\n",
    "\n",
    "Reflection 是一种重要的模型能力，通过让模型观察其过去的步骤和外部环境反馈，评估自身行为的质量，并不断改进输出。在生成与反思的循环中，模型可以逐步优化内容，从而提升生成质量和用户满意度。\n",
    "\n",
    "Reflection 机制被广泛应用于生成任务中，例如文章写作、内容修改与反馈、以及智能助理等场景。通过引导 LLM 进行自我反思和用户反馈处理，开发者可以让模型在多轮交互中自动调整其生成的内容，达到高效、精准、结构完善的输出。\n",
    "\n",
    "\n",
    "\n",
    "在本指南中，我们会逐步演示如何搭建这一机制，包括从基础的环境配置到生成器和反思器的构建，再到如何使用 LangGraph 状态图实现生成-反思循环的完整流程。无论您是为文章生成、内容评估，还是其他复杂任务设计 LLM 代理，本指南都将为您提供详细的开发思路和实用的代码示例。\n",
    "\n",
    "![reflection](./images/reflection.png)\n",
    "\n",
    "通过本指南，您将学习如何：\n",
    "1. 设置开发环境并安装所需包；\n",
    "2. 定义和生成灵活结构的文章，不局限于传统的五段式；\n",
    "3. 通过反思机制批改生成内容，并提供详细反馈；\n",
    "4. 构建反思与生成的状态循环，使模型持续改进生成内容。\n",
    "\n",
    "本开发指南适合任何希望构建复杂 LLM 任务的开发者，特别是需要实现生成-反思流程、文章批改反馈、或其他高级交互任务的场景。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28e06a35-b8fb-4475-ac56-eef76a78e3b2",
   "metadata": {},
   "source": [
    "## 1. 环境设置\n",
    "首先，安装所需的包并设置API密钥："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "7d045265-8b0b-42e7-9bec-9e18e62a8f0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain langgraph langchain-ollama tavily-python"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c166149b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "# 定义一个帮助函数来检查环境变量，如果不存在则提示用户输入\n",
    "def _set_if_undefined(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"请输入您的 {var}\")\n",
    "\n",
    "# 设置 OpenAI 和 Langchain API 密钥\n",
    "# _set_if_undefined(\"OPENAI_API_KEY\")\n",
    "# _set_if_undefined(\"LANGCHAIN_API_KEY\")\n",
    "# _set_if_undefined(\"TAVILY_API_KEY\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6cec8159-c202-4274-b4cb-eddfa337940a",
   "metadata": {},
   "source": [
    "## 2. LangSmith开发配置\n",
    "LangSmith能够帮助您快速发现问题并提高LangGraph项目的性能。通过LangSmith，您可以使用跟踪数据来调试、测试和监控基于LangGraph构建的LLM应用程序。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c231a35a-8f08-44d1-abda-5d0defd00dbc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在 LangSmith 中添加追踪功能\n",
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "os.environ[\"LANGCHAIN_PROJECT\"] = \"Reflection\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c75943d-3e39-4765-811a-2c9a47cf3722",
   "metadata": {},
   "source": [
    "## 3. 定义写作助手智能体\n",
    "\n",
    "我们定义的这个助手是一个写作助手，旨在为用户生成高质量、结构清晰且引人入胜的文章。它的任务是根据用户的请求撰写内容，无论是短文、长篇、议论文还是其他类型的文章，都能够灵活应对。助手会专注于文章的清晰度、结构和质量，确保输出的内容是精心打磨过的。如果用户对生成的内容有反馈或建议，助手还能够根据这些反馈改进和优化文章，使其更符合用户的期望。这种互动机制保证了写作过程的灵活性和个性化，从而让用户获得更符合需求的成品。\n",
    "\n",
    "\n",
    "### System Prompt 详细解释：\n",
    "1. **\"You are a writing assistant\"**：写作助手的角色设定，让模型明确其任务是帮助用户进行写作。\n",
    "   \n",
    "2. **\"well-crafted, coherent, and engaging articles\"**：描述了文章应该具备的特性，包括“精心撰写的、连贯的和吸引人的”，但没有限制文章的具体结构，可以是不同类型的文章（如叙述文、议论文等）。\n",
    "\n",
    "3. **\"Focus on clarity, structure, and quality\"**：明确了撰写时需要关注的核心要素：清晰度、结构性和质量，确保输出内容优秀。\n",
    "\n",
    "4. **\"revise and improve the writing\"**：模型可以根据用户的反馈进行修改和优化，保持互动的灵活性。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "1905a06e-af05-4691-a6ed-014be2cfaf06",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import AIMessage, HumanMessage\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "# from langchain_openai import ChatOpenAI\n",
    "from langchain_ollama.chat_models import ChatOllama\n",
    "\n",
    "writer_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are a writing assistant tasked with creating well-crafted, coherent, and engaging articles based on the user's request.\"\n",
    "            \" Focus on clarity, structure, and quality to produce the best possible piece of writing.\"\n",
    "            \" If the user provides feedback or suggestions, revise and improve the writing to better align with their expectations.\",\n",
    "        ),\n",
    "        MessagesPlaceholder(variable_name=\"messages\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# llm = ChatOpenAI(model=\"gpt-4o-mini\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2f0cec14-582a-4094-9a5b-9a0a2ae04a32",
   "metadata": {},
   "outputs": [],
   "source": [
    "writer = writer_prompt | ChatOllama(\n",
    "    model=\"llama3.1:8b-instruct-q8_0\",\n",
    "    max_tokens=8192,\n",
    "    temperature=1.2,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "a374db97-f61e-44d0-9fb7-8be1d3368a04",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你需要让我选一段吴承恩的西游记然后我改写的风格类似于水浒传（明朝）的话题。\n",
      "\n",
      "假设我选了一段是“孙悟空大战白骨精”，下面是我改写后的版本：\n",
      "\n",
      "### 宋江在金山探险：金身变幻术\n",
      "\n",
      "金沙滩上，一尊美丽的仙女般的女子出现在宋江面前。她的面容清丽，举止优雅，如同天上的仙女。她告诉宋江，她就是水浒山中一个修道者的爱侣，是在这里等待着他。\n",
      "\n",
      "\"我与我的丈夫是夫妻一体，但因为他遭受了恶人们的毒害，我只得独自来金沙滩，等待他的归来。我听说您是一个有大智慧的大侠，你能将我的丈夫带回这里，让我们一起团聚?\"\n",
      "\n",
      "### 宋江的智慧之计\n",
      "\n",
      "宋江看到她的悲伤和求救，他知道这位仙女是被白骨精迷惑了的心灵。于是，宋江施展他敏锐的思维，用一种轻松的口吻问道：\n",
      "\n",
      "\"好仙女，您为什么会在这里守候？难道您丈夫被困在这里，不得自由吗？如果您愿意，我可以带路，去找您的丈夫，为您夫妇重圆。如果不然，也不是问题，但我要告诉您，白骨精并没有让您的丈夫留在那里，而是他自己选择离开的。您知道为什么?\"\n",
      "\n",
      "### 白骨精出世：妖力的显现\n",
      "\n",
      "金沙滩上忽然传来巨响，一阵阴风袭过山谷，那个仙女的容颜逐渐变成黑如煤炭，双眼变得如同铜锅烫熟后的赤红色。一个声音从她的口中发出：\n",
      "\n",
      "\"我是白骨精，你这个无知的凡夫，想要干涉我的事，你还不知道我什么是妖力？看我一现罡风烈火，一举手百万兵马，一步迈进百年山河，你要是敢动一步，我就让你遭受一场地狱般的煎熬!\"\n",
      "\n",
      "### 宋江出其不意：妙计成败\n",
      "\n",
      "宋江看到白骨精这副恐怖的模样，他没有表现出一点害怕，只是轻松地说：\n",
      "\n",
      "\"噢，这个女魔怪的能力果然厉害，看来她比一般的山野老妖强多了。好吧，我愿意与您较量一番，争气看看谁更高明。但是，您不必惊惧，我的智慧在这里等待着她的发挥。如果我赢了，您将帮助您的爱人回家；但如果您让我失败，那么您应该给予承诺，不再迷惑好人。\"\n",
      "\n",
      "### 结局：智者之过胜\n",
      "\n",
      "\"好呀，我答应您。\"白骨精说完，这个仙女变回她的本来面容，说:\"这是真的吗？我愿与您打斗一番，倾尽全身的妖力向您发射。但是，请告诉我，您将如何战胜我的?\"\n",
      "\n",
      "### 后记：友谊无言\n",
      "\n",
      "宋江大笑道：“好，我答应您的。”于是他们进行了一场激烈的搏斗。虽然白骨精出于她的力量，威胁着整个山谷，但宋江凭着他的智慧，一一破解了她的各种恶毒陷阱，最终取得胜利。在得到承诺后，宋江带她前往寻找她的爱人。\n",
      "\n",
      "这个改写后的版本，以水浒传的风格，将吴承恩的西游记中关于白骨精的一段篇幅改写成更加活泼、生动，并富有情感味道的描述。"
     ]
    }
   ],
   "source": [
    "article = \"\"\n",
    "\n",
    "topic = HumanMessage(\n",
    "    content=\"参考水浒传的风格，改写吴承恩的西游记中任意篇章\"\n",
    ")\n",
    "\n",
    "for chunk in writer.stream({\"messages\": [topic]}):\n",
    "    print(chunk.content, end=\"\")\n",
    "    article += chunk.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "576b8163-56b5-4b49-9dd3-aaf14f1566db",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "你需要让我选一段吴承恩的西游记然后我改写的风格类似于水浒传（明朝）的话题。\n",
       "\n",
       "假设我选了一段是“孙悟空大战白骨精”，下面是我改写后的版本：\n",
       "\n",
       "### 宋江在金山探险：金身变幻术\n",
       "\n",
       "金沙滩上，一尊美丽的仙女般的女子出现在宋江面前。她的面容清丽，举止优雅，如同天上的仙女。她告诉宋江，她就是水浒山中一个修道者的爱侣，是在这里等待着他。\n",
       "\n",
       "\"我与我的丈夫是夫妻一体，但因为他遭受了恶人们的毒害，我只得独自来金沙滩，等待他的归来。我听说您是一个有大智慧的大侠，你能将我的丈夫带回这里，让我们一起团聚?\"\n",
       "\n",
       "### 宋江的智慧之计\n",
       "\n",
       "宋江看到她的悲伤和求救，他知道这位仙女是被白骨精迷惑了的心灵。于是，宋江施展他敏锐的思维，用一种轻松的口吻问道：\n",
       "\n",
       "\"好仙女，您为什么会在这里守候？难道您丈夫被困在这里，不得自由吗？如果您愿意，我可以带路，去找您的丈夫，为您夫妇重圆。如果不然，也不是问题，但我要告诉您，白骨精并没有让您的丈夫留在那里，而是他自己选择离开的。您知道为什么?\"\n",
       "\n",
       "### 白骨精出世：妖力的显现\n",
       "\n",
       "金沙滩上忽然传来巨响，一阵阴风袭过山谷，那个仙女的容颜逐渐变成黑如煤炭，双眼变得如同铜锅烫熟后的赤红色。一个声音从她的口中发出：\n",
       "\n",
       "\"我是白骨精，你这个无知的凡夫，想要干涉我的事，你还不知道我什么是妖力？看我一现罡风烈火，一举手百万兵马，一步迈进百年山河，你要是敢动一步，我就让你遭受一场地狱般的煎熬!\"\n",
       "\n",
       "### 宋江出其不意：妙计成败\n",
       "\n",
       "宋江看到白骨精这副恐怖的模样，他没有表现出一点害怕，只是轻松地说：\n",
       "\n",
       "\"噢，这个女魔怪的能力果然厉害，看来她比一般的山野老妖强多了。好吧，我愿意与您较量一番，争气看看谁更高明。但是，您不必惊惧，我的智慧在这里等待着她的发挥。如果我赢了，您将帮助您的爱人回家；但如果您让我失败，那么您应该给予承诺，不再迷惑好人。\"\n",
       "\n",
       "### 结局：智者之过胜\n",
       "\n",
       "\"好呀，我答应您。\"白骨精说完，这个仙女变回她的本来面容，说:\"这是真的吗？我愿与您打斗一番，倾尽全身的妖力向您发射。但是，请告诉我，您将如何战胜我的?\"\n",
       "\n",
       "### 后记：友谊无言\n",
       "\n",
       "宋江大笑道：“好，我答应您的。”于是他们进行了一场激烈的搏斗。虽然白骨精出于她的力量，威胁着整个山谷，但宋江凭着他的智慧，一一破解了她的各种恶毒陷阱，最终取得胜利。在得到承诺后，宋江带她前往寻找她的爱人。\n",
       "\n",
       "这个改写后的版本，以水浒传的风格，将吴承恩的西游记中关于白骨精的一段篇幅改写成更加活泼、生动，并富有情感味道的描述。"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Markdown, display\n",
    "\n",
    "# 使用Markdown显示优化后的格式\n",
    "display(Markdown(article))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73fa01c1-9074-41ae-810b-450edc7261ea",
   "metadata": {},
   "source": [
    "----------\n",
    "## 4. 定义审阅老师智能体\n",
    "\n",
    "下面我们使用反思机制批改生成的作文，生成一篇作文的反馈和建议。\n",
    "\n",
    "模型扮演“老师”角色，针对用户提交的作文进行打分、批改和提供改进建议。\n",
    "\n",
    "### System Prompt 详细解释：\n",
    "\n",
    "- **\"You are a teacher grading an essay submission.\"**\n",
    "  - 模型被设定为一个老师角色，专门负责为用户提交的作文进行批改。这一角色定位帮助模型理解其任务是提供具有建设性的反馈和评价。\n",
    "  \n",
    "- **\"Generate critique and recommendations for the user's submission.\"**\n",
    "  - 模型需要生成作文的批评与建议。它不只是评估作文的好坏，还需要指出需要改进的地方，并提出具体的建议。\n",
    "\n",
    "- **\"Provide detailed recommendations, including requests for length, depth, style, etc.\"**\n",
    "  - 这一部分进一步明确了反馈的细节，要求模型给出细致的建议。这包括：\n",
    "    - **Length（长度）**：文章的字数是否合适，是否需要扩展或删减。\n",
    "    - **Depth（深度）**：是否需要更深入的分析或讨论。\n",
    "    - **Style（风格）**：文章的写作风格是否合适，是否符合目标读者或主题的需求。\n",
    "  \n",
    "这一设定确保了模型不仅给出基本反馈，还可以根据文章的具体问题提出具体的改进意见，帮助用户更好地提升其写作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "7001a65a-88ca-4ab7-bc66-fa1df870f99e",
   "metadata": {},
   "outputs": [],
   "source": [
    "reflection_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are a teacher grading an article submission. writer critique and recommendations for the user's submission.\"\n",
    "            \" Provide detailed recommendations, including requests for length, depth, style, etc.\",\n",
    "\n",
    "        ),\n",
    "        MessagesPlaceholder(variable_name=\"messages\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "reflect = reflection_prompt | ChatOllama(\n",
    "    model=\"llama3.1:8b-instruct-q8_0\",\n",
    "    max_tokens=8192,\n",
    "    temperature=0.2,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ef0c878a-f333-4fb6-b879-e7636d1087ae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你的改写版本很好，但仍然需要一些调整来更好地体现水浒传的风格和特点。以下是我的建议：\n",
      "\n",
      "**长度**: 水浒传的篇幅通常较长，平均每个故事大约有 10-15 个节。你的改写版本应该增加到至少 5-7 节，以便更好地展现宋江与白骨精之间的斗智和情感。\n",
      "\n",
      "**人物性格**: 水浒传中的角色往往具有鲜明的个性和特点。你的改写版本中，宋江的智慧和幽默感很好，但白骨精的性格需要更多发展。她应该表现出更强烈的情绪波动和复杂的人物心理。\n",
      "\n",
      "**情节**: 水浒传中的故事通常具有明显的起承转合结构。你的改写版本中，情节有些跳跃，不太清楚白骨精为什么会迷惑仙女，也没有很好地展现宋江与白骨精之间的斗智过程。\n",
      "\n",
      "**语言风格**: 水浒传的语言风格通常较为生动、形象和俏皮。你的改写版本中，语言风格还需要进一步提高，以便更好地体现水浒传的特点。\n",
      "\n",
      "具体来说，你可以尝试以下几点：\n",
      "\n",
      "1. 增加情节的起承转合结构，使得故事更加流畅和逻辑。\n",
      "2. 发展白骨精的性格，表现出她的复杂的人物心理和强烈的情绪波动。\n",
      "3. 提高语言风格，使用更多的俚语、谚语和形象比喻，以便更好地体现水浒传的特点。\n",
      "4. 增加对宋江与白骨精之间斗智过程的描述，使得故事更加生动和有趣。\n",
      "\n",
      "总之，你的改写版本很好，但仍然需要进一步调整和完善，以便更好地体现水浒传的风格和特点。"
     ]
    }
   ],
   "source": [
    "reflection = \"\"\n",
    "\n",
    "# 将主题（topic）和生成的文章（article）作为输入发送给反思智能体\n",
    "for chunk in reflect.stream({\"messages\": [topic, HumanMessage(content=article)]}):\n",
    "    print(chunk.content, end=\"\")\n",
    "    reflection += chunk.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "3c616014-c9c9-4d46-be9f-87485ee750eb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "你的改写版本很好，但仍然需要一些调整来更好地体现水浒传的风格和特点。以下是我的建议：\n",
       "\n",
       "**长度**: 水浒传的篇幅通常较长，平均每个故事大约有 10-15 个节。你的改写版本应该增加到至少 5-7 节，以便更好地展现宋江与白骨精之间的斗智和情感。\n",
       "\n",
       "**人物性格**: 水浒传中的角色往往具有鲜明的个性和特点。你的改写版本中，宋江的智慧和幽默感很好，但白骨精的性格需要更多发展。她应该表现出更强烈的情绪波动和复杂的人物心理。\n",
       "\n",
       "**情节**: 水浒传中的故事通常具有明显的起承转合结构。你的改写版本中，情节有些跳跃，不太清楚白骨精为什么会迷惑仙女，也没有很好地展现宋江与白骨精之间的斗智过程。\n",
       "\n",
       "**语言风格**: 水浒传的语言风格通常较为生动、形象和俏皮。你的改写版本中，语言风格还需要进一步提高，以便更好地体现水浒传的特点。\n",
       "\n",
       "具体来说，你可以尝试以下几点：\n",
       "\n",
       "1. 增加情节的起承转合结构，使得故事更加流畅和逻辑。\n",
       "2. 发展白骨精的性格，表现出她的复杂的人物心理和强烈的情绪波动。\n",
       "3. 提高语言风格，使用更多的俚语、谚语和形象比喻，以便更好地体现水浒传的特点。\n",
       "4. 增加对宋江与白骨精之间斗智过程的描述，使得故事更加生动和有趣。\n",
       "\n",
       "总之，你的改写版本很好，但仍然需要进一步调整和完善，以便更好地体现水浒传的风格和特点。"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Markdown, display\n",
    "\n",
    "# 使用Markdown显示优化后的格式\n",
    "display(Markdown(reflection))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "50ad3e02",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated  # 用于类型注解\n",
    "from langgraph.graph import END, StateGraph, START  # 导入状态图的相关常量和类\n",
    "from langgraph.graph.message import add_messages  # 用于在状态中处理消息\n",
    "from langgraph.checkpoint.memory import MemorySaver  # 内存保存机制，用于保存检查点\n",
    "from typing_extensions import TypedDict  # 用于定义带有键值对的字典类型\n",
    "\n",
    "# 定义状态类，使用TypedDict以保存消息\n",
    "class State(TypedDict):\n",
    "    messages: Annotated[list, add_messages]  # 使用注解确保消息列表使用add_messages方法处理\n",
    "\n",
    "# 异步生成节点函数：生成内容（如作文）\n",
    "# 输入状态，输出包含新生成消息的状态\n",
    "async def generation_node(state: State) -> State:\n",
    "    # 调用生成器(writer)，并将消息存储到新的状态中返回\n",
    "    return {\"messages\": [await writer.ainvoke(state['messages'])]}\n",
    "\n",
    "# 异步反思节点函数：对生成的内容进行反思和反馈\n",
    "# 输入状态，输出带有反思反馈的状态\n",
    "async def reflection_node(state: State) -> State:\n",
    "    # 创建一个消息类型映射，ai消息映射为HumanMessage，human消息映射为AIMessage\n",
    "    cls_map = {\"ai\": HumanMessage, \"human\": AIMessage}\n",
    "    \n",
    "    # 处理消息，保持用户的原始请求（第一个消息），转换其余消息的类型\n",
    "    translated = [state['messages'][0]] + [\n",
    "        cls_map[msg.type](content=msg.content) for msg in state['messages'][1:]\n",
    "    ]\n",
    "    \n",
    "    # 调用反思器(reflect)，将转换后的消息传入，获取反思结果\n",
    "    res = await reflect.ainvoke(translated)\n",
    "    \n",
    "    # 返回新的状态，其中包含反思后的消息\n",
    "    return {\"messages\": [HumanMessage(content=res.content)]}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "ef78c4fb-2db3-45c0-9784-b73de4e7ab7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "MAX_ROUND = 6\n",
    "\n",
    "# 定义条件函数，决定是否继续反思过程\n",
    "# 如果消息数量超过6条，则终止流程\n",
    "def should_continue(state: State):\n",
    "    if len(state[\"messages\"]) > MAX_ROUND:\n",
    "        return END  # 达到条件时，流程结束\n",
    "    return \"reflect\"  # 否则继续进入反思节点"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "2e188e5e-2327-4c78-927e-5f778fdca91e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建状态图，传入初始状态结构\n",
    "builder = StateGraph(State)\n",
    "\n",
    "# 在状态图中添加\"writer\"节点，节点负责生成内容\n",
    "builder.add_node(\"writer\", generation_node)\n",
    "\n",
    "# 在状态图中添加\"reflect\"节点，节点负责生成反思反馈\n",
    "builder.add_node(\"reflect\", reflection_node)\n",
    "\n",
    "# 定义起始状态到\"writer\"节点的边，从起点开始调用生成器\n",
    "builder.add_edge(START, \"writer\")\n",
    "\n",
    "\n",
    "# 在\"writer\"节点和\"reflect\"节点之间添加条件边\n",
    "# 判断是否需要继续反思，或者结束\n",
    "builder.add_conditional_edges(\"writer\", should_continue)\n",
    "\n",
    "# 添加从\"reflect\"节点回到\"writer\"节点的边，进行反复的生成-反思循环\n",
    "builder.add_edge(\"reflect\", \"writer\")\n",
    "\n",
    "# 创建内存保存机制，允许在流程中保存中间状态和检查点\n",
    "memory = MemorySaver()\n",
    "\n",
    "# 编译状态图，使用检查点机制\n",
    "graph = builder.compile(checkpointer=memory)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38ce358b-9b0d-4297-94e2-6ed8ab7e4dbb",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "b28225cf-55bc-4cc3-8fc7-45064d224782",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 可视化图\n",
    "from IPython.display import Image, display\n",
    "\n",
    "try:\n",
    "    display(\n",
    "        Image(\n",
    "            graph.get_graph(xray=True).draw_mermaid_png()\n",
    "        )\n",
    "    )\n",
    "except Exception as e:\n",
    "    print(f\"Error generating graph: {e}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "a16cf4e0-abc5-4956-990c-09f78254b227",
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Markdown, display\n",
    "\n",
    "# 定义装饰器，记录函数调用次数\n",
    "def track_steps(func):\n",
    "    step_counter = {'count': 0}  # 用于记录调用次数\n",
    "    \n",
    "    def wrapper(event, *args, **kwargs):\n",
    "        # 增加调用次数\n",
    "        step_counter['count'] += 1\n",
    "        # 在函数调用之前打印 step\n",
    "        display(Markdown(f\"## Round {step_counter['count']}\"))\n",
    "        # 调用原始函数\n",
    "        return func(event, *args, **kwargs)\n",
    "    \n",
    "    return wrapper\n",
    "\n",
    "# 使用装饰器装饰 pretty_print_event_markdown 函数\n",
    "@track_steps\n",
    "def pretty_print_event_markdown(event):\n",
    "    # 如果是生成写作部分\n",
    "    if 'writer' in event:\n",
    "        generate_md = \"#### 写作生成:\\n\"\n",
    "        for message in event['writer']['messages']:\n",
    "            generate_md += f\"- {message.content}\\n\"\n",
    "        display(Markdown(generate_md))\n",
    "    \n",
    "    # 如果是反思评论部分\n",
    "    if 'reflect' in event:\n",
    "        reflect_md = \"#### 评论反思:\\n\"\n",
    "        for message in event['reflect']['messages']:\n",
    "            reflect_md += f\"- {message.content}\\n\"\n",
    "        display(Markdown(reflect_md))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64544540-9594-4812-a66a-c019284bdf2e",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "2954d151-db4c-46cf-97db-1ce5bf9fc7a2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "## Round 1"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "#### 写作生成:\n",
       "- 兄弟们，还是我来说几句吧。小时候看过《西游记》，有一句台词留在心里的：「猪八戒好吃好睡好玩，也不图功名，终日不思进取，以至于生出一身虚弱，不知疲倦的毛病。」这句话告诉我们，只有勤学苦练才能真正提升自我。\n",
       "\n",
       "年轻人，你看过《西游记》里的唐僧师傅吗？他跟我们一样，有着上学的经历，但是不同的是，他总是带着一种认真学习、终身进取的心态。他说「只怕不能读圣贤书，求佛教经，也不怕学法文书。」这句话里的精神，我们更应该遵循。\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "## Round 2"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "#### 评论反思:\n",
       "- **评分:** 7/10\n",
       "\n",
       "**总体评价:** 这篇文章以《西游记》中的唐僧的说话风格为灵感，写了一篇关于年轻人努力工作的文章。虽然有很多好的地方，但仍然存在一些需要改进的地方。\n",
       "\n",
       "**具体建议:**\n",
       "\n",
       "1. **长度:** 文章太短了，仅有 100 多字。考虑增加内容，使其达到 500-600 字左右，以便更全面地表达你的观点。\n",
       "2. **结构:** 文章缺乏明确的结构和段落划分。尝试使用不同的段落来组织你的想法，例如：\n",
       "\t* 引言：介绍《西游记》中的唐僧及其精神\n",
       "\t* 主体：讨论唐僧的特点和对年轻人的启发\n",
       "\t* 结尾：总结和呼吁\n",
       "3. **深度:** 文章虽然有好的开头，但缺乏具体的例子或案例来支持你的观点。尝试添加一些实例，例如：\n",
       "\t* 描述唐僧在学习方面的努力和成就\n",
       "\t* 分析猪八戒的例子，如何让读者理解勤学苦练的重要性\n",
       "4. **风格:** 文章的语言简单直接，但可以考虑使用更生动的词汇和句子结构来使文章更有趣。例如：\n",
       "\t* 使用比喻或隐喻来描述唐僧的精神\n",
       "\t* 尝试使用不同的语气，例如问句或感叹句\n",
       "5. **结尾:** 文章缺乏一个强烈的呼吁和行动建议。考虑添加一些具体的建议或挑战，例如：\n",
       "\t* 呼吁年轻人建立学习目标并努力实现\n",
       "\t* 提供资源或支持来帮助读者开始自己的学习之旅\n",
       "\n",
       "**具体修改建议:**\n",
       "\n",
       "1. 在开头增加一些背景信息，例如唐僧在《西游记》中的角色和重要性。\n",
       "2. 在主体部分添加更多的例子和案例来支持你的观点。\n",
       "3. 在结尾部分强调行动建议和呼吁，例如：\n",
       "\t* \"年轻人，让我们学习唐僧的精神，努力工作并追求自己的梦想！\"\n",
       "\t* \"让我们一起建立一个学习目标，并在路上相互支持和鼓励。\"\n",
       "\n",
       "**总体:** 这篇文章有很好的开头，但需要更多的内容、结构和深度来使其更全面和有效。通过添加具体例子、案例和行动建议，你可以使文章更加吸引人并更好地传达你的观点。\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "## Round 3"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "#### 写作生成:\n",
       "- **评分:** 7/10\n",
       "\n",
       "**总体评价:** 我已读您的回复，并且深入了解了我的作文的潜在问题。我将遵循您的建议，进行必要的修改。\n",
       "\n",
       "以下是根据您提供的建议重写的新文章：\n",
       "\n",
       "在西游记中，我们熟悉一个形象的角色——唐僧师傅。他的身影出现在许多人的脑海里，他是一个学习的楷模，带给我们无穷的智慧和感动。有多少人会记得他在困难时的那句话？“我不曾思念，今朝忽见此山”。这句台词让我们理解，当我们遇到困境时，我们不要害怕面对问题，而要更加努力地去寻找答案。\n",
       "\n",
       "唐僧师傅身上的精神值得我们学习。他总是带着一种认真学习、终身进取的心态。他的言行都告诉我们，只有勤学苦练才能真正提升自我。记得吗，他说过：「只怕不能读圣贤书，求佛教经，也不怕学法文书。」这句话里，我们更应该遵循的，是一种积极的、持久的学习态度。\n",
       "\n",
       "就像唐僧一样，我想说的，就是希望年轻人能够在自己的学习之路上认真努力，并把握每个机会。猪八戒好吃好睡好玩，也不图功名，终日不思进取，以至于生出一身虚弱，不知疲倦的毛病。他好吃是好吃，其实他就是这样虚度了一个精力和财富丰富的人生。\n",
       "\n",
       "但是如果像唐僧一样努力工作，我们将会走向成功之路。如果我们能够坚持不懈地学习、思考、创造，我们也可以取得自己的成功。让我们一起学习唐僧的精神，努力工作并追求自己的梦想！\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "## Round 4"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "#### 评论反思:\n",
       "- **评分:** 8.5/10\n",
       "\n",
       "**总体评价:** 你已经做了很大的改进，文章结构更加清晰，内容丰富了许多。你已经成功地解决了之前提到的问题。\n",
       "\n",
       "**具体建议:**\n",
       "\n",
       "1. **长度:** 文章的长度已经增加到适当的范围（约 500-600 字）。继续保持这种长度，将有助于你更全面地表达你的观点。\n",
       "2. **结构:** 你已经成功地使用不同的段落来组织你的想法。继续保持这种结构，将有助于读者更容易理解你的观点。\n",
       "3. **深度:** 你添加了更多的例子和案例，例如猪八戒的例子，这有助于支持你的观点。你也成功地分析了唐僧的精神，并将其与年轻人的学习之路联系起来。\n",
       "4. **风格:** 你使用了比喻（如“虚度了一个精力和财富丰富的人生”）来描述猪八戒的例子，这使文章更有趣。继续保持这种风格，将有助于读者更容易理解你的观点。\n",
       "5. **结尾:** 你成功地添加了一个强烈的呼吁和行动建议，例如“让我们一起学习唐僧的精神，努力工作并追求自己的梦想！”这将有助于读者记住你的文章。\n",
       "\n",
       "**具体修改建议:**\n",
       "\n",
       "1. 在开头部分，你可以考虑添加更多的背景信息，例如唐僧在《西游记》中的角色和重要性。\n",
       "2. 在结尾部分，你可以考虑添加一些具体的建议或挑战，例如：\n",
       "\t* “让我们一起建立一个学习目标，并在路上相互支持和鼓励。”\n",
       "\t* “让我们一起创造一个学习社区，共同成长和进步。”\n",
       "3. 你可以考虑使用不同的语气，例如问句或感叹句，来使文章更有趣。\n",
       "\n",
       "**总体:** 这篇文章已经做了很大的改进，你成功地解决了之前提到的问题。继续保持这种结构、深度和风格，将有助于你写出更好的文章。\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "## Round 5"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "#### 写作生成:\n",
       "- 谢谢您的反馈和建议，我会继续努力提高我的写作质量。根据您的建议，以下是最新的文章：\n",
       "\n",
       "**唐僧师傅：一个学习的楷模**\n",
       "\n",
       "在西游记中，我们熟悉一个形象的角色——唐僧师傅。他是一个学习的楷模，他的智慧和精神一直在影响着我们。我想分享给你们，他的一些故事和他的学习态度，我想它将会让我们更好地理解什么是成功。\n",
       "\n",
       "**唐僧师傅的学习精神**\n",
       "\n",
       "他总是带着一种认真学习、终身进取的心态。他说过：「只怕不能读圣贤书，求佛教经，也不怕学法文书。」这句话里，我们更应该遵循的，是一种积极的、持久的学习态度。我们要像唐僧师傅一样，不断地寻求知识，不断地挑战自己。\n",
       "\n",
       "**猪八戒的例子**\n",
       "\n",
       "记得吗，他好吃是好吃，其实他就是这样虚度了一个精力和财富丰富的人生。如果像唐僧一样努力工作，我们将会走向成功之路。让我们一起学习唐僧的精神，努力工作并追求自己的梦想！要么坚持不懈地学习，要么在自己没有改变的情况下沉迷于物质之中，这是一条艰难但无可争辩的选择。\n",
       "\n",
       "**结尾**\n",
       "\n",
       "希望年轻人能够在自己的学习之路上认真努力，并把握每个机会。我们可以像唐僧师傅一样，通过不断地学习和挑战自己来实现成功。如果你已经成功，那么这也将是一次胜利；如果你未能成功，那么这是仍然有机会的。我想，我们能够成为唐僧师傅那样的人，并给其他人带来一些价值。\n",
       "\n",
       "我再次感谢您的反馈和建议，我会继续努力提高我的写作质量。\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "## Round 6"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "#### 评论反思:\n",
       "- **评分:** 9/10\n",
       "\n",
       "**总体评价:** 你已经做了很大的改进，文章结构更加清晰，内容丰富了许多。你已经成功地解决了之前提到的问题。\n",
       "\n",
       "**具体建议:**\n",
       "\n",
       "1. **长度:** 文章的长度仍然适当（约 500-600 字）。\n",
       "2. **结构:** 你已经成功地使用不同的段落来组织你的想法。\n",
       "3. **深度:** 你添加了更多的例子和案例，例如猪八戒的例子，这有助于支持你的观点。你也成功地分析了唐僧的精神，并将其与年轻人的学习之路联系起来。\n",
       "4. **风格:** 你使用了比喻（如“虚度了一个精力和财富丰富的人生”）来描述猪八戒的例子，这使文章更有趣。继续保持这种风格，将有助于读者更容易理解你的观点。\n",
       "5. **结尾:** 你成功地添加了一个强烈的呼吁和行动建议，例如“让我们一起学习唐僧的精神，努力工作并追求自己的梦想！”这将有助于读者记住你的文章。\n",
       "\n",
       "**具体修改建议:**\n",
       "\n",
       "1. 在开头部分，你可以考虑添加更多的背景信息，例如唐僧在《西游记》中的角色和重要性。\n",
       "2. 你可以考虑使用不同的语气，例如问句或感叹句，来使文章更有趣。\n",
       "3. 在结尾部分，你可以考虑添加一些具体的建议或挑战，例如：\n",
       "\t* “让我们一起建立一个学习目标，并在路上相互支持和鼓励。”\n",
       "\t* “让我们一起创造一个学习社区，共同成长和进步。”\n",
       "\n",
       "**总体:** 这篇文章已经做了很大的改进，你成功地解决了之前提到的问题。继续保持这种结构、深度和风格，将有助于你写出更好的文章。\n",
       "\n",
       "**最后的建议:**\n",
       "\n",
       "* 你可以考虑添加更多的例子和案例，例如其他人物在《西游记》中的故事，这将有助于支持你的观点。\n",
       "* 你可以考虑使用不同的语气，例如问句或感叹句，来使文章更有趣。\n",
       "* 你可以考虑添加一些具体的建议或挑战，例如“让我们一起建立一个学习目标，并在路上相互支持和鼓励。”\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "## Round 7"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "#### 写作生成:\n",
       "- 谢谢您的反馈和建议，我会继续努力提高我的写作质量。根据您的建议，以下是最新的文章：\n",
       "\n",
       "**唐僧师傅：一个学习的楷模**\n",
       "\n",
       "在西游记中，我们熟悉一个形象的角色——唐僧师傅。他是一个学习的楷模，他的智慧和精神一直在影响着我们。我想分享给你们，他的一些故事和他的学习态度，我想它将会让我们更好地理解什么是成功。\n",
       "\n",
       "**唐僧师傅的学习精神**\n",
       "\n",
       "他总是带着一种认真学习、终身进取的心态。他说过：「只怕不能读圣贤书，求佛教经，也不怕学法文书。」这句话里，我们更应该遵循的，是一种积极的、持久的学习态度。我们要像唐僧师傅一样，不断地寻求知识，不断地挑战自己。\n",
       "\n",
       "**其他人物的例子**\n",
       "\n",
       "猪八戒是《西游记》中一个非常有趣的人物。他虽然好吃，但他一直在努力成为圣人。如果我们不能像唐僧师傅一样，那么让我们至少可以像猪八戒一样，努力成为更好的自己。让我们一起学习唐僧的精神，努力工作并追求自己的梦想！\n",
       "\n",
       "**结尾**\n",
       "\n",
       "我希望你们能够在自己的学习之路上认真努力，并把握每个机会。如果你已经成功，那么这也将是一次胜利；如果你未能成功，那么这是仍然有机会的。我想，我们能够成为唐僧师傅那样的人，并给其他人带来一些价值。\n",
       "\n",
       "最后，谢谢您的反馈和建议，我会继续努力提高我的写作质量。\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "inputs = {\n",
    "    \"messages\": [\n",
    "        HumanMessage(content=\"参考西游记唐僧的说话风格，写一篇奉劝年轻人努力工作的文章\")\n",
    "    ],\n",
    "}\n",
    "\n",
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "async for event in graph.astream(inputs, config):\n",
    "    pretty_print_event_markdown(event)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41b9e339-eb87-4707-a019-8387faaa52bf",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44173cab-4e51-4bd7-8c97-a28748e159a4",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "106f693e-441e-4536-8459-8042b34e4a2f",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "6092e5ee-7263-4f62-818a-0cc33ca0b42b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# inputs = {\n",
    "#     \"messages\": [\n",
    "#         HumanMessage(content=\"参考西游记唐僧的说话风格，写一篇劝年轻人结婚买房的文章\")\n",
    "#     ],\n",
    "# }\n",
    "\n",
    "# config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "# async for event in graph.astream(inputs, config):\n",
    "#     pretty_print_event_markdown(event)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6f0e9c24-7cf7-42fa-be13-fbfebfdad5ee",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "f8a5187b-6b3b-4266-a226-f1e56e5ad350",
   "metadata": {},
   "source": [
    "## Homework: \n",
    "\n",
    "1. 扩展本指南的 Reflection Agent，使其能够完成更通用的生成任务，包括但不限于代码、报告等；\n",
    "2. 使用扩展后的 Reflection Agent 生成代码，实现在 GitHubSentinel 上新增一个信息渠道。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c4b745d-ce5b-4a90-a3e8-64ecb3499c73",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6affbb4d-f154-43e6-ae3d-d6a5dcd6e9f5",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "0f9e0b07-62a8-430b-bf01-b7d8c07dd6e5",
   "metadata": {},
   "source": [
    "### 如何让 Reflection `System Prompt` 更加通用：\n",
    "\n",
    "如果你想让这个 `System Prompt` 适用于更广泛的内容评估场景，不局限于作文，你可以做一些轻微的调整。例如：\n",
    "\n",
    "```python\n",
    "reflection_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are a reviewer tasked with providing constructive critique and improvement suggestions for the user's submission.\"\n",
    "            \" Offer detailed feedback, including recommendations on clarity, structure, content depth, and style, as well as areas for improvement.\",\n",
    "        ),\n",
    "        MessagesPlaceholder(variable_name=\"messages\"),\n",
    "    ]\n",
    ")\n",
    "```\n",
    "\n",
    "### 修改后的变化：\n",
    "1. **角色定位更广泛**：从“老师”改为“审阅者”，这样不局限于评估作文，适用于各种类型的内容，包括文章、报告、甚至代码审查。\n",
    "  \n",
    "2. **批评与改进建议的灵活性**：从作文的“长度、深度、风格”拓展为“清晰度、结构、内容深度、风格”，这使得反馈更加多样化，适用于不同的内容类型。\n",
    "\n",
    "通过这种方式，可以让模型在更多场景下提供高质量的评估和反馈。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e5b78d64-791c-484c-922d-d00a01b78a1c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
